<template><div><h1 id="浏览器的事件循环" tabindex="-1"><a class="header-anchor" href="#浏览器的事件循环" aria-hidden="true">#</a> 浏览器的事件循环</h1>
<!--  -->
<h2 id="浏览器的进程模型" tabindex="-1"><a class="header-anchor" href="#浏览器的进程模型" aria-hidden="true">#</a> 浏览器的进程模型</h2>
<p>  程序运行需要有他自己的专属的内存空间，可以把这块内存空间简单的理解为进程。</p>
<p>  每个应用至少拥有一个进程，进程之间相互独立即使要通信，也要需要双方同意。</p>
<h2 id="何为线程" tabindex="-1"><a class="header-anchor" href="#何为线程" aria-hidden="true">#</a> 何为线程？</h2>
<p>  有了进程后，就可以运行代码了。</p>
<p>  而运行代码的这个“人”称之为线程。</p>
<p>  一个进程至少有一个线程。所以在进程开启后会自动创建一个线程来运行代码，该线程称之为主线程。</p>
<p>  如果程序需要同时执行多块代码，主线程就会启动更多的线程来执行代码，所以一个进程中可以包含多个线程。</p>
<h2 id="浏览器又那些进程和线程" tabindex="-1"><a class="header-anchor" href="#浏览器又那些进程和线程" aria-hidden="true">#</a> 浏览器又那些进程和线程？</h2>
<p><strong>浏览器是一个多进程多进程的应用程序</strong></p>
<p>  浏览器内部工作及其复杂。</p>
<p>  为了避免相互影响，为了减少连环崩溃的几率，他会自动启动多个进程。</p>
<div class="hint-container tip">
<p class="hint-container-title">可以在浏览器的任务管理器中查看当前的所有进程。</p>
</div>
<p>其中，最主要的进程有：</p>
<p>  1、浏览器进程</p>
<p>  主要负责界面显示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务。</p>
<p>  2、网络进程</p>
<p>  负责加载网络资源，网络进程内部会启动多个线程来处理不同的网络任务。</p>
<p>  3、<strong>渲染进程</strong></p>
<p>  渲染进程启动后会开启一个<strong>渲染主线程</strong>，主线程负责执行 HTML、CSS、JS 代码。</p>
<p>  默认情况下，浏览器会为每个标签页开启一个新的渲染进程，以保证不同的标签页之间互不影响。</p>
<div class="hint-container tip">
<p class="hint-container-title">将来该默认模式可能会有所改变，有兴趣可参见 chrome 官方说明文档</p>
</div>
<h2 id="渲染主线程是如何工做的" tabindex="-1"><a class="header-anchor" href="#渲染主线程是如何工做的" aria-hidden="true">#</a> 渲染主线程是如何工做的？</h2>
<p>渲染主线程是浏览器中最繁忙的线程，需要它处理的任务包括但不限于：</p>
<ul>
<li>解析 HTML</li>
<li>解析 CSS</li>
<li>计算样式</li>
<li>布局</li>
<li>处理样式</li>
<li>每秒把页面画 60 次</li>
<li>执行全局 JS 代码</li>
<li>执行事件处理函数</li>
<li>执行计时器的回调函数</li>
<li>......</li>
</ul>
<div class="hint-container tip">
<p class="hint-container-title">思考题：为什么渲染进程不适用多线程来处理这些事情？</p>
</div>
<p>  要处理这么多任务，主线程遇到了一个前所未有的难题：如何调度任务？</p>
<p>比如：</p>
<ul>
<li>我正在执行一个 JS 函数，执行到一半的时候用户点击了按钮，我该立即去执行点击事件的处理函数吗？</li>
<li>我正在执行一个 JS 函数，执行到一半的时候某个计时器到达了时间，我该立即去执行它的回调吗？</li>
<li>浏览器进程通知我“用户点击了按钮”，与此同时，某个计时器也到达了时间，我应该处理哪一个呢？</li>
<li>......</li>
</ul>
<p>  渲染主线程想出了一个绝妙的主意来处理这个问题：排队</p>
<p>  1、在每次开始的时候，渲染主线程会进入一个无线循环。</p>
<p>  2、每一次循环会检查消息队列是否有任务存在，如果有，就取出第一个任务执行，执行完成后进入下一次循环；如果没有则进入休眠状态。</p>
<p>  3、其他所有线程（包括其他进程的线程）可以随时向消息队列添加任务。新任务会加到消息队列的末尾。在添加新任务时，如果主线程是休眠状态，则会将其唤醒继续循环拿取任务。</p>
<p>  这样一来，就可以让每个任务有条不紊的、持续的进行下去了。</p>
<p>  <strong>整个过程，被称之为事件循环（消息循环）</strong></p>
<h2 id="何为异步" tabindex="-1"><a class="header-anchor" href="#何为异步" aria-hidden="true">#</a> 何为异步？</h2>
<p>  在代码执行过程中，会遇到一些无法立即处理的任务，比如：</p>
<ul>
<li>计时完成后需要执行的代码任务 -- setTimeout、setInterval</li>
<li>网络通信任务完成后执行的任务 -- XHR、Fetch</li>
<li>用户操作后需要执行的任务 --- addEventListener</li>
</ul>
<p>  如果让渲染主线程等待这些任务的时机达到，就会导致主线程长期处于<strong>阻塞</strong>的状态，从而导致浏览器<strong>卡死</strong></p>
<p>   <strong>渲染主线程承担及其重要的工作，无论如何都不能阻塞！</strong></p>
<p>  因此，浏览器选择异步来解决这个问题。</p>
<p>  使用异步的方式，渲染主线成永不阻塞。</p>
<div class="hint-container tip">
<p class="hint-container-title">面试题：如何理解 JS 的异步？</p>
<p>参考：</p>
<p>  JS 是一门单线程的语言，这是因为它运行在浏览器的渲染主线程中，而渲染主线程只有一个。</p>
<p>  并且渲染主线程承担着诸多工作，渲染页面、执行 JS 都在其中运行。</p>
<p>  如果使用同步的方式就极有可能导致主线程产生阻塞，从而导致消息队列中的很多其他任务无法得到执行。</p>
<p>  这样一来，一方面会导致繁忙的主线程拜拜的消耗时间，另一方面导致页面无法及时更新，给用户造成卡死现象。</p>
<p>  所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时，比如计时器、网络、事件监听，主线程将回调任务交给其他线程去处理，自身立即结束任务的执行，转而执行后续的代码。当其他线程完成时，将事先传递的回调函数包装成任务，加入到消息队列的末尾排队等待主线程调度执行。</p>
<p>  在这种异步模式下浏览器用不阻塞，从而最大限度的保证了单线程的流畅运行。</p>
</div>
<h2 id="js-为何会阻碍渲染" tabindex="-1"><a class="header-anchor" href="#js-为何会阻碍渲染" aria-hidden="true">#</a> JS 为何会阻碍渲染？</h2>
<h2 id="任务有优先级吗" tabindex="-1"><a class="header-anchor" href="#任务有优先级吗" aria-hidden="true">#</a> 任务有优先级吗？</h2>
<p>  任务没有优先级，在消息队列中先进先出</p>
<p>  根据 W3C 的最新解释：</p>
<ul>
<li>
<p>每个任务都有一个任务类型，同一类型的任务必须在一个队列，不同类型的任务可以分属不同的队列。在一次事件循环中浏览器可以根据实际情况从不同的队列中取出任务执行。</p>
</li>
<li>
<p>浏览器必须准备好一个微队列，微队列中的任务优先所有其他任务执行。</p>
</li>
</ul>
<p>  <a href="https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint" target="_blank" rel="noopener noreferrer">https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint<ExternalLinkIcon/></a></p>
<p>  在目前 chrome 的实现中，至少包含下面的队列：</p>
<ul>
<li>延时队列：用于存放计时器达到后的回调任务，优先级&lt;中&gt;</li>
<li>交互队列：用于存放用户操作后产生的时间处理任务，优先级&lt;高&gt;</li>
<li>微队列：用户存放需要最快执行的任务，优先级&lt;最高&gt;</li>
</ul>
<div class="hint-container tip">
<p class="hint-container-title">添加任务到微队列的主要方式主要是使用 Promise、MutationObserver</p>
<p>列如：</p>
<div class="language-javascript line-numbers-mode" data-ext="js"><pre v-pre class="language-javascript"><code><span class="token comment">//立即把一个函数添加到微队列</span>
Promise<span class="token punctuation">.</span><span class="token function">resovle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>函数<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div></div>
<div class="hint-container tip">
<p class="hint-container-title">浏览器还有很多其他的队列，由于和我们关系不大，不做考虑</p>
</div>
<div class="hint-container tip">
<p class="hint-container-title">面试题：阐述一下 JS 的事件循环</p>
<p>参考：</p>
<p>  事件循环又叫做消息循环，是浏览器渲染主线程的工作方式。</p>
<p>  在 chrome 的源码中，它开启一个不会结束的 for 循环，每一次从消息队列中取出第一个任务执行，而其他线程只需要在核实的时候将任务加入到队列的末尾即可。</p>
<p>  过去把消息队列简单分位宏队列和微队列，这种说法目前已无法满足复杂的浏览器环境，取而代之的是一种更加灵活多变的处理方式。</p>
<p>  根据 W3C 的官方文档的解释，每个任务有不同的类型，同类型的任务必须在同一个队列，不同的任务可以属于不同的队列，不同的任务队列有不同的优先级，在一次循环中，由浏览器自行决定取哪一个队列的任务，但浏览器必须有一个微队列，微队列的任务一定具有最高的优先级，必须优先调度执行。</p>
</div>
<div class="hint-container tip">
<p class="hint-container-title">面试题：JS 中的计时器能做到精确计时吗？为什么？</p>
<p>参考：</p>
<p>  不行因为</p>
<p>  1、计算机硬件没有原子钟，无法做到精确计时。</p>
<p>  2、操作系统的计时函数本身就有少量的偏差，由于 JS 的计时器最终调用的是操作系统的函数，也就携带了这些偏差。</p>
<p>  3、按照 W3C 的标准，浏览器实现计时器，如果嵌套层级超过 5 层，则会带有 4 毫秒的最少时间，这样在计时时间少于 4 毫秒时又带来了偏差。</p>
<p>  4、受事件循环的影响，计时器的回调只能在主线程空闲时运行，因此又带来了偏差。</p>
</div>
</div></template>


